/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  ArkMethod,
  Stmt,
  ArkClass,
  AbstractInvokeExpr,
  ArkInvokeStmt,
  ArkAssignStmt,
} from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from "arkanalyzer/lib/utils/logger";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects,IssueReport } from "../../model/Defects";
import {
  ClassMatcher,
  MatcherCallback,
  MatcherTypes,
} from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

const logger = Logger.getLogger(
  LOG_MODULE_TYPE.HOMECHECK,
  "NoUselessConstructorCheck"
);
const gMetaData: BaseMetaData = {
  severity: 1,
  ruleDocPath: "docs/foreach-args-check.md", // TODO: support url
  description: "Suggestion: NoUselessConstructor.",
};
//不必要的构造函数包括：空的构造函数，或者构造函数中直接执行父类构造函数的逻辑。
export class NoUselessConstructorCheck implements BaseChecker {
  readonly metaData: BaseMetaData = gMetaData;
  readonly constructor_ = "constructor";
  readonly super_ = "super";
  public rule: Rule;
  public defects: Defects[] = [];
  public issues: IssueReport[] = [];
  private clsMatcher: ClassMatcher = {
    matcherType: MatcherTypes.CLASS,
  };
  public registerMatchers(): MatcherCallback[] {
    const matchBuildCb: MatcherCallback = {
      matcher: this.clsMatcher,
      callback: this.check,
    };
    return [matchBuildCb];
  }

  public check = (targetArkClass: ArkClass) => {
    const severity = this.rule.alert ?? this.metaData.severity;
    this.arkClassProcess(targetArkClass, severity);
  };

  private arkClassProcess(arkClass: ArkClass, severity: number) {
    const arkMeths: ArkMethod[] = arkClass.getMethods();
    const superClassName: string = arkClass.getSuperClassName();
    for (const arkMethod of arkMeths) {
      const code = arkMethod.getCode() ?? "";
      //不是构造方法直接跳过
      if (!this.checkAccessibilityConstructor(code, superClassName)) {
        continue;
      }
      let args = arkMethod.getParameters().map((param) => param.getName()); //由于构造函数的特殊性可以用长度推算出stmt的长度
      const stmts = arkMethod.getBody()?.getCfg().getStmts() ?? [];
      let lineCode = arkMethod.getLine() ?? 0;
      let lineColCode = arkMethod.getColumn() ?? 0;
      //记录构造方法头
      const headConstructStmt = stmts[0];
      this.modifyArrayStmts(stmts, args.length); //获取构造方法内逻辑-----args.length为参数赋值stmts
      //1.构造函数方法体为空 && 没有继承其它class
      if (stmts.length == 0 && superClassName == "") {
        this.addIssueReport_01(
          [lineCode, lineColCode, code],
          headConstructStmt,
          this.constructor_,
          severity
        );
        continue;
      }
      let stmtsNumber = 0; //记录执行遇到super()时已经执行的stmt数量
      let superStmt: Stmt = headConstructStmt;
      let superaArgs: string[] = []; //与构造参数进行比较
      for (const stmt of stmts) {
        const invokeExpr = this.getInvokeExprFromStmt(stmt);
        if (!invokeExpr) {
          stmtsNumber++;
          continue;
        }
        const methodSign = invokeExpr.getMethodSignature();
        const className = methodSign
          .getDeclaringClassSignature()
          .getClassName();
        const methodName = methodSign.getMethodSubSignature().getMethodName();
        const args = invokeExpr.getArgs();
        const originPosition = stmt.getOriginPositionInfo();
        const line = originPosition.getLineNo();
        if (methodName === this.super_) {
          superStmt = stmt;
          for (const arg of args) {
            let argcode = arg.toString().replace(/^...\s*/, "");
            superaArgs.push(argcode);
          }
          break;
        }
        stmtsNumber++;
      }
      //2.当只有super()并且都无参数 或者 参数相同(明字+数量)----->无奈之举:无法获到usedStmts[]
      if (stmts.length == 1 && stmtsNumber == 0) {
        //判断(明字+数量)---->明确条件逻辑拆分
        const arraysAreEqual = (arr1: string[], arr2: string[]) =>
          JSON.stringify(arr1.sort()) === JSON.stringify(arr2.sort());
        if (superaArgs.length == 0 && args.length == 0) {
          this.addIssueReport(superStmt, this.super_, severity);
        }
        if (
          superaArgs.length > 0 &&
          superaArgs.length == args.length &&
          arraysAreEqual(superaArgs, args)
        ) {
          this.addIssueReport(superStmt, this.super_, severity);
        }
      }
    }
  }

  //判定是否是符合要求的构造方法名
  private checkAccessibilityConstructor(
    text: string,
    superClassname: string
  ): boolean {
    const constructorIndex = text.search(/\bconstructor\s*\(/);
    if (constructorIndex === -1) {
      return false;
    }

    const startString = text.substring(0, constructorIndex).trim();
    switch (startString) {
      case "protected":
      case "private":
        return false;
      case "public":
        if (superClassname !== "") {
          return false;
        }
        break;
    }
    return true;
  }

  // 获取构造方法体内的stmts和构造方法内的参数stmts
  modifyArrayStmts(arr: any[], countToRemove: number): any[] {
    // 去除数组最后一个元素
    arr.pop();

    // 从第一个位置开始分割指定数量的元素+2后的为方法内部stmts
    const methodArryStmt = arr.splice(0, countToRemove + 2);
    methodArryStmt.slice(1, -1); //去掉多余stmts
    return methodArryStmt;
  }
  //是否是执行语句
  getInvokeExprFromStmt(stmt: Stmt): AbstractInvokeExpr | null {
    if (stmt instanceof ArkInvokeStmt) {
      return stmt.getInvokeExpr();
    } else if (stmt instanceof ArkAssignStmt) {
      const rightOp = stmt.getRightOp();
      if (rightOp instanceof AbstractInvokeExpr) {
        return rightOp;
      }
    }
    return null;
  }

  private addIssueReport(stmt: Stmt, name: string, severity: number) {
    const warnInfo = this.getLineAndColumn(stmt, name);
    const fixKey =
      warnInfo.line + "%" + warnInfo.startCol + "%" + this.rule.ruleId;
    const mergeKey =
      warnInfo.filePath + "%" + fixKey + "%" + this.metaData.description;
    let defect = 
      new Defects(
        warnInfo.line,
        warnInfo.startCol,
        this.metaData.description,
        severity,
        this.metaData.ruleId,
        mergeKey,
        this.metaData.ruleDocPath,
        true,
        false,
        false,
        fixKey
      );
    this.issues.push(new IssueReport(defect, undefined));
    RuleListUtil.push(
      new Defects(
        warnInfo.line,
        warnInfo.startCol,
        this.metaData.description + name,
        severity,
        this.rule.ruleId,
        mergeKey,
        this.metaData.ruleDocPath,
        true,
        false,
        false,
        fixKey
      )
    );
  }

  private addIssueReport_01(
    linePos: [x: number, y: number, label: string],
    stmt: Stmt,
    name: string,
    severity: number
  ) {
    const warnInfo = this.getLineAndColumnMethod(linePos, stmt, name);
    const fixKey =
      warnInfo.line + "%" + warnInfo.startCol + "%" + this.rule.ruleId;
    const mergeKey =
      warnInfo.filePath + "%" + fixKey + "%" + this.metaData.description;
    this.defects.push(
      new Defects(
        warnInfo.line,
        warnInfo.startCol,
        this.metaData.description,
        severity,
        this.metaData.ruleId,
        mergeKey,
        this.metaData.ruleDocPath,
        true,
        false,
        false,
        fixKey
      )
    );
    RuleListUtil.push(
      new Defects(
        warnInfo.line,
        warnInfo.startCol,
        this.metaData.description + name,
        severity,
        this.rule.ruleId,
        mergeKey,
        this.metaData.ruleDocPath,
        true,
        false,
        false,
        fixKey
      )
    );
  }

  private getLineAndColumn(stmt: Stmt, name: string) {
    const originPosition = stmt.getOriginPositionInfo();
    const line = originPosition.getLineNo();
    const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
    if (arkFile) {
      const originText = stmt.getOriginalText() ?? "";
      let startCol = originPosition.getColNo();
      const pos = originText.indexOf(name);
      if (pos !== -1) {
        startCol += pos;
        const endCol = startCol + name.length - 1;
        const originPath = arkFile.getFilePath();
        return { line, startCol, endCol, filePath: originPath };
      }
    } else {
      logger.debug("originStmt or arkFile is null");
    }
    return { line: -1, startCol: -1, endCol: -1, filePath: "" };
  }
  
  private getLineAndColumnMethod(
    linePos: [line: number, col: number, code: string],
    stmt: Stmt,
    name: string
  ) {
    const line = linePos[0];
    const arkFile = stmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
    if (arkFile) {
      let startCol = linePos[1];
      const pos = linePos[2].indexOf(name);
      if (pos !== -1) {
        startCol += pos;
        const endCol = startCol + name.length - 1;
        const originPath = arkFile.getFilePath();
        return { line, startCol, endCol, filePath: originPath };
      }
    } else {
      logger.debug("originStmt or arkFile is null");
    }
    return { line: -1, startCol: -1, endCol: -1, filePath: "" };
  }
}
